import asyncio
from collections import defaultdict

import ujson
from fastapi.encoders import jsonable_encoder
from peewee import DoesNotExist

from common.g.g import G_LOGGER, G_ID_WORKER, G_MQ
from common.model.mall.spu import SPU, SKU
from common.model.order.order import Order, OrderItem
from common.utils.depends.common import Page
from common.utils.resp import RC
from common.v.v import DATAORCODE
from common.pkg.str_id import ten_to_any
from common.pkg.rabbitmq_util import DelayMsgQueue
from enums.order_enum import OrderSourceEnum, OrderTypeEnum, OrderStatusEnum, OrderPayStatusEnum, OrderReturnStatusEnum, \
    OrderShippingStatusEnum, OrderComplaintStatusEnum, OrderItemReturnStatusEnum, OrderItemShippingStatusEnum, \
    OrderItemComplaintStatusEnum, ClientQueryOrderStatusEnum
from enums.spu_enum import SPUPriceTypeEnum
from enums.stock_enum import StockTypeEnum
from schema.inner_use_schema.ali_pay_schema import AliPayTradeQueryResponseModel
from schema.req_schema.order_schema import AddNewOrderModel, AddressInfoModel
from service.base import Service
from service.spu_service import SPUService, SKUService, StockService
from service.transaction_service import TransactionService


class _OrderCheck:

    def __init__(self, spu_info_list, sku_info_list, req_model, spu_stock_info_list, dt):
        self.spu_info_list = spu_info_list
        self.sku_info_list = sku_info_list
        self.req_model = req_model
        self.spu_stock_info_list = spu_stock_info_list

        self.spu_id_to_info = {s["spu_id"]: s for s in spu_info_list}
        self.db_spu_to_sku = defaultdict(set)
        for i in sku_info_list:
            self.db_spu_to_sku[i["spu_id"]].add(i["sku_id"])

        self.spu_to_stock_type = {i["spu_id"]: i["stock_type"] for i in spu_stock_info_list}

        self.price_point_dict = {
            i["sku_id"]: {
                "sale_price": i["sale_price"] or 0,
                "cost_point": i["cost_point"] or 0
            } for i in sku_info_list
        }
        self.req_quantity = {
            f"{each.spu_id}_{each.sku_id}_{each.date}_{each.start_time}_{each.end_time}": each.quantity
            for each in self.req_model.order_item_list
        }

        self.dt = dt

        self.order_summary = spu_info_list[0]["name"]

    def check_spu(self):
        if len(self.req_model.spu_id_list) != len(self.spu_info_list):
            return False, DATAORCODE(data=None, code=RC.NOT_EXIST, msg="有商品不存在")
        return True, DATAORCODE()

    def check_sku(self):
        if len(self.req_model.sku_id_list) != len(self.sku_info_list):
            return False, DATAORCODE(data=None, code=RC.NOT_EXIST, msg="有商品不存在")
        return True, DATAORCODE()

    def check_exist_stock(self):
        for spu_id in self.req_model.spu_id_list:
            if spu_id not in self.spu_to_stock_type:
                return False, DATAORCODE(data=None, code=RC.FORBIDDING, msg=f"商品:{spu_id}暂无库存")
        return True, DATAORCODE()

    def check_spu_is_match_sku(self):
        # 7. 检查spu信息和sku信息是否匹配
        for db_spu_id, db_sku_set in self.db_spu_to_sku.items():
            req_sku_set = set(self.req_model.spu_id_to_sku_id_list[db_spu_id])
            if req_sku_set != db_sku_set:
                G_LOGGER.error(f"req_sku_set={req_sku_set} db_sku_set={db_sku_set}")
                return False, DATAORCODE(data=None, code=RC.FORBIDDING, msg=f"商品:{db_spu_id}信息异常")
        return True, DATAORCODE()

    def check_spu_onsale_status(self):
        for each in self.spu_info_list:
            spu = SPU(**each)
            if spu.is_onsale(self.dt) is False:
                return False, DATAORCODE(data=None, code=RC.FORBIDDING, msg=f"商品:{each.spu_id}不在售卖时间")
        return True, DATAORCODE()

    def check_sku_onsale_status(self):
        for each in self.sku_info_list:
            sku = SKU(**each)
            if sku.is_onsale(self.dt) is False:
                return False, DATAORCODE(data=None, code=RC.FORBIDDING, msg=f"商品:{each.sku_id}不在售卖时间")
        return True, DATAORCODE()

    def check_param_by_stock_type(self):
        first_stock_type = None
        for each_item in self.req_model.order_item_list:
            stock_type = self.spu_to_stock_type[each_item.spu_id]
            if first_stock_type is None:
                first_stock_type = stock_type
            else:
                if first_stock_type != stock_type:
                    return False, DATAORCODE(data=None, code=RC.FORBIDDING, msg="不同库存类型的商品不可以同时下单")
            if stock_type == StockTypeEnum.date:
                if not each_item.date:
                    return False, DATAORCODE(data=None, code=RC.PARAM_MISSING, msg="缺失参数")
            elif stock_type == StockTypeEnum.time:
                if not all([each_item.date, each_item.start_time, each_item.end_time]):
                    return False, DATAORCODE(data=None, code=RC.PARAM_MISSING, msg="缺失参数")
        return True, DATAORCODE()

    def check_spu_type(self):
        # 实物商品和虚拟商品不能一起下单
        spu_type = None
        for spu in self.spu_info_list:
            current_spu_type = spu["spu_type"]
            if spu_type is None:
                spu_type = current_spu_type
            else:
                if spu_type != current_spu_type:
                    return False, DATAORCODE(data=None, code=RC.FORBIDDING, msg="实物商品和虚拟产品不能一起下单")
        return True, DATAORCODE()

    def get_order_type(self):
        price_type_set = set()
        for spu in self.spu_info_list:
            price_type_set.add(spu["price_type"])
        if (SPUPriceTypeEnum.money in price_type_set and
            SPUPriceTypeEnum.point in price_type_set) or \
                SPUPriceTypeEnum.money_and_point in price_type_set:
            return OrderTypeEnum.money_and_point
        else:
            if SPUPriceTypeEnum.money in price_type_set:
                return OrderTypeEnum.money
            else:
                return OrderTypeEnum.point

    def check(self):
        for check_func in ["check_spu", "check_spu_type", "check_sku", "check_spu_is_match_sku", "check_exist_stock",
                           "check_param_by_stock_type", "check_spu_onsale_status", "check_sku_onsale_status"]:
            func = getattr(self, check_func)
            if func:
                ok, res = func()
                if not ok:
                    return ok, res
        return True, DATAORCODE()


class OrderService(Service):

    def _get_order_sn(self):
        snow_id = G_ID_WORKER.get_id()
        return ten_to_any(snow_id, 62)

    async def _get_addr_info(self, user_id, addr_info: AddressInfoModel):
        addrs = {}
        if not addr_info:
            return {}
        if not addr_info.address_id:
            ...
        else:
            ...
        return addrs

    async def create_order(self, mall_id, user_id, req_model: AddNewOrderModel, dt):
        # 2. 获取spu信息 sku信息 以及spu库存信息
        spu_s = SPUService(self.mgw, self.redis)
        sku_s = SKUService(self.mgw, self.redis)
        ss = StockService(self.mgw, self.redis)
        spu_info_list, sku_info_list, spu_stock_info = await asyncio.gather(
            spu_s.get_detail_by_spu_id_list(mall_id,
                                            req_model.spu_id_list,
                                            fields=[
                                                "spu_id", "onsale", "start_timestamp", "end_timestamp", "name",
                                                "detail_images", "price_type", "spu_type"
                                            ]),
            sku_s.get_detail_by_sku_id_list(mall_id,
                                            req_model.sku_id_list),
            ss.get_spu_stock_type_info(mall_id,
                                       req_model.spu_id_list),
            return_exceptions=False
        )

        oc = _OrderCheck(spu_info_list, sku_info_list, req_model, spu_stock_info, dt)
        ok, res = oc.check()
        if not ok:
            return ok, res
        # 8. 检查库存是否足够
        req_data = jsonable_encoder(req_model.dict(exclude_unset=True, exclude_none=True))
        order_item_list = list(req_data["order_item_list"])
        res = await ss.get_stock(mall_id, condition_list=list(order_item_list))

        server_key_to_quantity = {}
        for each in res:
            d = each.get("date")
            if str(d) == "1970-01-01":
                date = None
            else:
                date = str(each.get("date"))
            key = f"{each['spu_id']}_{each['sku_id']}_{date}_{each.get('start_time')}_{each.get('end_time')}"
            server_key_to_quantity[key] = {
                "quantity": each["quantity"],
                "prehold_quantity": each["prehold_quantity"],
                "stock_id": each["stock_id"]
            }

        stock_id_to_prehold_quantity = {}  # 存放需要扣减的库存
        for k, need_quantity in oc.req_quantity.items():
            if k not in server_key_to_quantity:
                return False, DATAORCODE(data=None, code=RC.QUANTITY_NOT_ENOUGH, msg="商品暂无库存")
            if server_key_to_quantity[k]["quantity"] - server_key_to_quantity[k]["prehold_quantity"] < need_quantity:
                return False, DATAORCODE(data=None, code=RC.QUANTITY_NOT_ENOUGH, msg="商品库存不足")
            stock_id_to_prehold_quantity[server_key_to_quantity[k]["stock_id"]] = need_quantity
        # 9. 计算价格
        total_amount = 0
        total_point = 0
        for each in req_model.order_item_list:
            total_amount += each.quantity * oc.price_point_dict[each.sku_id]["sale_price"]
            total_point += each.quantity * oc.price_point_dict[each.sku_id]["cost_point"]
        if req_model.precheck:
            return True, DATAORCODE(data={"total_amount": total_amount, "total_point": total_point})
        else:
            async with self.mgw.atomic() as f:
                try:
                    # 预扣库存
                    update_row_list = await ss.prehold_quantity(stock_id_to_prehold_quantity)
                    if not all(update_row_list):
                        await f.rollback()
                        return False, DATAORCODE(data=None, code=RC.QUANTITY_NOT_ENOUGH, msg="扣减库存失败")
                    # 生成订单
                    order_sn = self._get_order_sn()
                    addrs = await self._get_addr_info(user_id, req_model.address_info)
                    order_data = {
                        "mall_id": mall_id,
                        "order_source": OrderSourceEnum.mall,
                        "order_sn": order_sn,
                        "order_type": oc.get_order_type(),
                        "order_status": OrderStatusEnum.active,
                        "pay_status": OrderPayStatusEnum.unpay,
                        "return_status": OrderReturnStatusEnum.can_return,
                        "shipping_status": OrderShippingStatusEnum.wait_deliver,
                        "complaint_status": OrderComplaintStatusEnum.no_refund,
                        "total_amount": total_amount,
                        "cost_point": total_point,
                        "user_id": user_id,
                        "buyer_words": req_model.buyer_words,
                        "order_summary": oc.order_summary,
                        **addrs
                    }
                    order_id = await Order.add_new_instance(self.mgw, **order_data)
                    order_item_data_list = []
                    for each in req_model.order_item_list:
                        k = f"{each.spu_id}_{each.sku_id}_{each.date}_{each.start_time}_{each.end_time}"
                        d = each.dict(exclude_unset=True, exclude_none=True)
                        order_item_data_list.append({
                            "order_id": order_id,
                            "mall_id": mall_id,
                            "spu_name": oc.spu_id_to_info[each.spu_id]["name"],
                            "stock_id": server_key_to_quantity[k]["stock_id"],
                            "item_return_status": OrderItemReturnStatusEnum.can_return,
                            "item_shipping_status": OrderItemShippingStatusEnum.wait_deliver,
                            "item_complaint_status": OrderItemComplaintStatusEnum.no_refund,
                            "total_amount": each.quantity * oc.price_point_dict[each.sku_id]["sale_price"],
                            "cost_point": each.quantity * oc.price_point_dict[each.sku_id]["cost_point"],
                            **d
                        })
                    await self.mgw.execute(OrderItem.insert_many(order_item_data_list))

                    # 发送消息到队列
                    msg = ujson.dumps({
                        "order_id": order_id,
                        "mall_id": mall_id,
                        "user_id": user_id
                    })
                    dmq = DelayMsgQueue(G_MQ,
                                        exchange_name="mall_order",
                                        routing_key="unpay_order",
                                        un_used_queue_name="unpay_dead_queue_1",
                                        dead_queue_name="unpay_order",
                                        dead_exchange_name="unpay_dead_exchange",
                                        dead_exchange_routing_key="unpay_order_routing_key",
                                        ttl=3 * 60)
                    await dmq.send_delay_msg(msg, 1)
                    return True, DATAORCODE(data={"order_id": order_id, "order_sn": order_sn})
                except Exception as e:
                    await f.rollback()
                    G_LOGGER.exception(e)
                    return False, DATAORCODE(data=None, code=RC.DB_ERROR, msg="下单失败")

    async def _get_order_stock_info(self, order_id):
        """
        获取订单使用的库存信息，回滚用

        Returns:

        """
        query = OrderItem.select().where(OrderItem.order_id == order_id)
        oi_list = await self.mgw.execute(query)
        stock_id_to_quantity = {}
        for oi in oi_list:
            stock_id_to_quantity[oi.stock_id] = oi.quantity
        return stock_id_to_quantity

    async def cancel_order(self, mall_id, order_sn, user_id):
        ss = StockService(self.mgw, self.redis)
        async with self.mgw.atomic() as f:
            try:
                oq = Order.select().for_update().where(Order.mall_id == mall_id,
                                                       Order.order_sn == order_sn,
                                                       Order.user_id == user_id,
                                                       Order.order_status == OrderStatusEnum.active,
                                                       Order.pay_status == OrderPayStatusEnum.unpay,  # 已经支付的订单不走取消吧？走售后
                                                       Order.removed == False)
                orders = await self.mgw.execute(oq)
                if not orders:
                    return False, DATAORCODE(data=None, code=RC.NOT_EXIST, msg="订单已经被取消或者不存在")
                order = orders[0]
                update_row = await Order.update_instance(self.mgw,
                                                 condition={"order_sn": order_sn, "version": order.version},
                                                 update_fields={"order_status": OrderStatusEnum.cancel,
                                                                "version": order.version + 1})
                if update_row:
                    stock_id_to_quantity = await self._get_order_stock_info(order.order_id)
                    update_row = await ss.rollback_quantity(stock_id_to_quantity)
                    if not all(update_row):
                        G_LOGGER.info(f"订单:{order.order_id}超时未支付库存回滚不完整,可能是商品库存被删除导致")
                return True, DATAORCODE()

            except DoesNotExist:
                return False, DATAORCODE(data=None, code=RC.NOT_EXIST, msg="订单已经被取消或者不存在")
            except Exception as e:
                await f.rollback()
                G_LOGGER.exception(e)
                return False, DATAORCODE(data=None, code=RC.DB_ERROR, msg="服务异常")

    async def handle_unpay_order(self, order_id):
        """
        处理未支付订单消息，
            如果当前订单已支付，那么暂时先不做处理
            如果当前订单未支付，说明超时未支付，需要回滚库存
        Args:
            order_id:
        Returns:
        """
        ss = StockService(self.mgw, self.redis)
        async with self.mgw.atomic() as f:
            try:
                oq = Order.select().for_update().where(Order.order_id == order_id)
                o = await self.mgw.get(oq)
                if o.order_status != OrderStatusEnum.discard and o.pay_status == OrderPayStatusEnum.unpay:
                    # 说明超时未支付
                    # 修改状态
                    update_row = await Order.update_instance(self.mgw, condition={
                        "order_id": order_id,
                        "version": o.version
                    }, update_fields={"version": o.version + 1, "order_status": OrderStatusEnum.discard})
                    if update_row:
                        # 回滚库存
                        stock_id_to_quantity = await self._get_order_stock_info(order_id)
                        update_row = await ss.rollback_quantity(stock_id_to_quantity)
                        if not all(update_row):
                            G_LOGGER.info(f"订单:{order_id}超时未支付库存回滚不完整,可能是商品库存被删除导致")
                    return True
                else:
                    # 说明已经支付，不做处理
                    # 或者c端主动取消，导致订单状态为作废
                    G_LOGGER.info(f"订单:{order_id}已经支付,不做处理")
                    return True
            except Exception as e:
                await f.rollback()
                G_LOGGER.exception(f"订单超时未支付库存回滚异常:{e}")
                return False

    async def get_order_info_for_pay(self, order_sn):
        try:
            res = await Order.get_instance(self.mgw, condition={"order_sn": order_sn})
        except DoesNotExist:
            return False, DATAORCODE(data=None, code=RC.DB_ERROR, msg="订单不存在")
        except Exception as e:
            G_LOGGER.exception(e)
            return False, DATAORCODE(data=None, code=RC.DB_ERROR, msg="服务异常")

        return True, DATAORCODE(data=Order(**res))

    async def get_order_info_for_check_pay(self, order_sn):
        try:
            res = await Order.get_instance(self.mgw, condition={"order_sn": order_sn}, fields=["order_id", "version", "order_sn", "mall_id"])
        except DoesNotExist:
            return False, DATAORCODE(data=None, code=RC.DB_ERROR, msg="订单不存在")
        except Exception as e:
            G_LOGGER.exception(e)
            return False, DATAORCODE(data=None, code=RC.DB_ERROR, msg="服务异常")
        return True, DATAORCODE(data=Order(**res))

    async def order_pay(self, order_sn, pay_data):
        """
        订单支付
        Args:
            order_sn: 订单sn
            pay_data: 订单支付信息
        Returns:
        """

    async def get_order_list(self, mall_id, page_info: Page, status: ClientQueryOrderStatusEnum, user_id: int = None):
        base_condition = [Order.mall_id == mall_id, Order.removed == False]
        if user_id:
            base_condition.append(Order.user_id == user_id)
        if status == ClientQueryOrderStatusEnum.WAIT_PAY:
            base_condition.extend([
                Order.order_status == OrderStatusEnum.active,
                Order.pay_status == OrderPayStatusEnum.unpay
            ])
        elif status == ClientQueryOrderStatusEnum.WAIT_DELIVER:
            base_condition.extend([
                Order.order_status == OrderStatusEnum.active,
                Order.pay_status == OrderPayStatusEnum.paid,
                Order.shipping_status == OrderShippingStatusEnum.wait_deliver,
                Order.complaint_status == OrderComplaintStatusEnum.no_refund
            ])
        elif status == ClientQueryOrderStatusEnum.WAIT_RECEIVE:
            base_condition.extend([
                Order.order_status == OrderStatusEnum.active,
                Order.pay_status == OrderPayStatusEnum.paid,
                Order.shipping_status == OrderShippingStatusEnum.wait_receive,
                Order.complaint_status == OrderComplaintStatusEnum.no_refund
            ])
        else:
            base_condition.extend([
                Order.order_status == OrderStatusEnum.active,
                Order.pay_status == OrderPayStatusEnum.paid,
                Order.shipping_status == OrderShippingStatusEnum.received,
                Order.complaint_status == OrderComplaintStatusEnum.no_refund
            ])

        query = Order.select().where(*base_condition)
        try:
            count = await self.mgw.count(query)
            data = await self.mgw.execute(query.paginate(page_info.page, page_info.page_size).dicts())
        except Exception as e:
            G_LOGGER.exception(e)
            return False, DATAORCODE(data=None, code=RC.DB_ERROR, msg="服务异常")
        return True, DATAORCODE(data={'data': data, "count": count})

    async def order_pay_by_ali(self, tenant_id, order_id, old_version, resp_model: AliPayTradeQueryResponseModel):
        order_sn = resp_model.out_trade_no
        async with self.mgw.atomic() as f:
            try:
                update_row = await Order.update_instance(self.mgw,
                                              condition={
                                                  "order_sn": order_sn,
                                                  "removed": False,
                                                  "order_status": OrderStatusEnum.active,
                                                  "pay_status": OrderPayStatusEnum.unpay,
                                                  "version": old_version
                                              },
                                              update_fields={
                                                  "version": old_version + 1,
                                                  "pay_status": OrderPayStatusEnum.paid,
                                                  "paid_amount": resp_model.total_amount
                                              }
                                              )
                if update_row:
                    query = OrderItem.update(
                        paid_amount=OrderItem.total_amount,
                        version=OrderItem.version+1
                    ).where(OrderItem.order_id==order_id, OrderItem.removed==False)
                    update_row2 = await self.mgw.execute(query)
                    # 记录支付记录
                    ts = TransactionService(self.mgw, self.redis)
                    await ts.add_new_ali_translation(tenant_id, resp_model)
            except Exception as e:
                await f.rollback()
                G_LOGGER.exception(e)
                return False, DATAORCODE(data=None, code=RC.DB_ERROR, msg="服务异常")
        return True, DATAORCODE()
